/*
 * %W% %E%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */

package javax.swing.plaf.basic;

import sun.swing.DefaultLookup;
import sun.swing.UIAction;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import javax.swing.plaf.*;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

import java.awt.Component;
import java.awt.Container;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Insets;
import java.awt.Graphics;
import java.awt.event.*;
import java.io.Serializable;
import java.awt.Toolkit;
import java.awt.ComponentOrientation;

/**
 * A default L&F implementation of ScrollPaneUI.
 *
 * @version %I% %G%
 * @author Hans Muller
 */
public class BasicScrollPaneUI
    extends ScrollPaneUI implements ScrollPaneConstants
{
    protected JScrollPane scrollpane;
    protected ChangeListener vsbChangeListener;
    protected ChangeListener hsbChangeListener;
    protected ChangeListener viewportChangeListener;
    protected PropertyChangeListener spPropertyChangeListener;
    private MouseWheelListener mouseScrollListener;

    /**
     * PropertyChangeListener installed on the vertical scrollbar.
     */
    private PropertyChangeListener vsbPropertyChangeListener;

    /**
     * PropertyChangeListener installed on the horizontal scrollbar.
     */
    private PropertyChangeListener hsbPropertyChangeListener;

    private Handler handler;

    /**
     * State flag that shows whether setValue() was called from a user program
     * before the value of "extent" was set in right-to-left component
     * orientation.
     */
    private boolean setValueCalled = false;


    public static ComponentUI createUI(JComponent x) {
	return new BasicScrollPaneUI();
    }

    static void loadActionMap(LazyActionMap map) {
        map.put(new Actions(Actions.SCROLL_UP));
	map.put(new Actions(Actions.SCROLL_DOWN));
	map.put(new Actions(Actions.SCROLL_HOME));
	map.put(new Actions(Actions.SCROLL_END));
	map.put(new Actions(Actions.UNIT_SCROLL_UP));
	map.put(new Actions(Actions.UNIT_SCROLL_DOWN));
        map.put(new Actions(Actions.SCROLL_LEFT));
        map.put(new Actions(Actions.SCROLL_RIGHT));
        map.put(new Actions(Actions.UNIT_SCROLL_RIGHT));
        map.put(new Actions(Actions.UNIT_SCROLL_LEFT));
    }



    public void paint(Graphics g, JComponent c) {
	Border vpBorder = scrollpane.getViewportBorder();
	if (vpBorder != null) {
	    Rectangle r = scrollpane.getViewportBorderBounds();
	    vpBorder.paintBorder(scrollpane, g, r.x, r.y, r.width, r.height);
	}
    }


    /**
     * @return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE)
     */
    public Dimension getMaximumSize(JComponent c) {
	return new Dimension(Short.MAX_VALUE, Short.MAX_VALUE);
    }


    protected void installDefaults(JScrollPane scrollpane) 
    {
	LookAndFeel.installBorder(scrollpane, "ScrollPane.border");
	LookAndFeel.installColorsAndFont(scrollpane, 
	    "ScrollPane.background", 
	    "ScrollPane.foreground", 
            "ScrollPane.font");

        Border vpBorder = scrollpane.getViewportBorder();
        if ((vpBorder == null) ||( vpBorder instanceof UIResource)) {
	    vpBorder = UIManager.getBorder("ScrollPane.viewportBorder");
	    scrollpane.setViewportBorder(vpBorder);
        }
        LookAndFeel.installProperty(scrollpane, "opaque", Boolean.TRUE);
    }


    protected void installListeners(JScrollPane c) 
    {
	vsbChangeListener = createVSBChangeListener();
        vsbPropertyChangeListener = createVSBPropertyChangeListener();
	hsbChangeListener = createHSBChangeListener();
        hsbPropertyChangeListener = createHSBPropertyChangeListener();
	viewportChangeListener = createViewportChangeListener();
	spPropertyChangeListener = createPropertyChangeListener();

	JViewport viewport = scrollpane.getViewport();
	JScrollBar vsb = scrollpane.getVerticalScrollBar();
	JScrollBar hsb = scrollpane.getHorizontalScrollBar();

	if (viewport != null) {
	    viewport.addChangeListener(viewportChangeListener);
	}
	if (vsb != null) {
	    vsb.getModel().addChangeListener(vsbChangeListener);
            vsb.addPropertyChangeListener(vsbPropertyChangeListener);
	}
	if (hsb != null) {
	    hsb.getModel().addChangeListener(hsbChangeListener);
            hsb.addPropertyChangeListener(hsbPropertyChangeListener);
	}

	scrollpane.addPropertyChangeListener(spPropertyChangeListener);

    mouseScrollListener = createMouseWheelListener();
    scrollpane.addMouseWheelListener(mouseScrollListener);

    }

    protected void installKeyboardActions(JScrollPane c) {
	InputMap inputMap = getInputMap(JComponent.
				  WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);

	SwingUtilities.replaceUIInputMap(c, JComponent.
			       WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, inputMap);

        LazyActionMap.installLazyActionMap(c, BasicScrollPaneUI.class,
                                           "ScrollPane.actionMap");
    }

    InputMap getInputMap(int condition) {
	if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
	    InputMap keyMap = (InputMap)DefaultLookup.get(scrollpane, this,
                                        "ScrollPane.ancestorInputMap");
	    InputMap rtlKeyMap;

	    if (scrollpane.getComponentOrientation().isLeftToRight() ||
		    ((rtlKeyMap = (InputMap)DefaultLookup.get(scrollpane, this,
                    "ScrollPane.ancestorInputMap.RightToLeft")) == null)) {
		return keyMap;
	    } else {
		rtlKeyMap.setParent(keyMap);
		return rtlKeyMap;
	    }
	}
	return null;
    }

    public void installUI(JComponent x) {
	scrollpane = (JScrollPane)x;
	installDefaults(scrollpane);
	installListeners(scrollpane);
	installKeyboardActions(scrollpane);
    }


    protected void uninstallDefaults(JScrollPane c) {
	LookAndFeel.uninstallBorder(scrollpane);

        if (scrollpane.getViewportBorder() instanceof UIResource) {
            scrollpane.setViewportBorder(null);
        }
    }


    protected void uninstallListeners(JComponent c) {
	JViewport viewport = scrollpane.getViewport();
	JScrollBar vsb = scrollpane.getVerticalScrollBar();
	JScrollBar hsb = scrollpane.getHorizontalScrollBar();

	if (viewport != null) {
	    viewport.removeChangeListener(viewportChangeListener);
	}
	if (vsb != null) {
	    vsb.getModel().removeChangeListener(vsbChangeListener);
            vsb.removePropertyChangeListener(vsbPropertyChangeListener);
	}
	if (hsb != null) {
	    hsb.getModel().removeChangeListener(hsbChangeListener);
            hsb.removePropertyChangeListener(hsbPropertyChangeListener);
	}

	scrollpane.removePropertyChangeListener(spPropertyChangeListener);

    if (mouseScrollListener != null) {
        scrollpane.removeMouseWheelListener(mouseScrollListener);
    }

	vsbChangeListener = null;
	hsbChangeListener = null;
	viewportChangeListener = null;
	spPropertyChangeListener = null;
        mouseScrollListener = null;
        handler = null;
    }


    protected void uninstallKeyboardActions(JScrollPane c) {
	SwingUtilities.replaceUIActionMap(c, null);
	SwingUtilities.replaceUIInputMap(c, JComponent.
			   WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
    }


    public void uninstallUI(JComponent c) {
	uninstallDefaults(scrollpane);
	uninstallListeners(scrollpane);
	uninstallKeyboardActions(scrollpane);
	scrollpane = null;
    }

    private Handler getHandler() {
        if (handler == null) {
            handler = new Handler();
        }
        return handler;
    }

    protected void syncScrollPaneWithViewport()
    {
	JViewport viewport = scrollpane.getViewport();
	JScrollBar vsb = scrollpane.getVerticalScrollBar();
	JScrollBar hsb = scrollpane.getHorizontalScrollBar();
	JViewport rowHead = scrollpane.getRowHeader();
	JViewport colHead = scrollpane.getColumnHeader();
	boolean ltr = scrollpane.getComponentOrientation().isLeftToRight();

	if (viewport != null) {
	    Dimension extentSize = viewport.getExtentSize();
	    Dimension viewSize = viewport.getViewSize();
	    Point viewPosition = viewport.getViewPosition();

	    if (vsb != null) {
		int extent = extentSize.height;
		int max = viewSize.height;
		int value = Math.max(0, Math.min(viewPosition.y, max - extent));
		vsb.setValues(value, extent, 0, max);
	    }

	    if (hsb != null) {
		int extent = extentSize.width;
		int max = viewSize.width;
		int value;

		if (ltr) {
		    value = Math.max(0, Math.min(viewPosition.x, max - extent));
		} else {
		    int currentValue = hsb.getValue();

		    /* Use a particular formula to calculate "value"
		     * until effective x coordinate is calculated.
		     */
		    if (setValueCalled && ((max - currentValue) == viewPosition.x)) {
			value = Math.max(0, Math.min(max - extent, currentValue));
			/* After "extent" is set, turn setValueCalled flag off.
			 */
			if (extent != 0) {
			    setValueCalled = false;
			}
		    } else {
			if (extent > max) {
			    viewPosition.x = max - extent;
			    viewport.setViewPosition(viewPosition);
			    value = 0;
			} else {
			   /* The following line can't handle a small value of
			    * viewPosition.x like Integer.MIN_VALUE correctly
			    * because (max - extent - viewPositoiin.x) causes
			    * an overflow. As a result, value becomes zero.
			    * (e.g. setViewPosition(Integer.MAX_VALUE, ...)
			    *       in a user program causes a overflow.
			    *       Its expected value is (max - extent).)
			    * However, this seems a trivial bug and adding a
			    * fix makes this often-called method slow, so I'll
			    * leave it until someone claims.
			    */
			    value = Math.max(0, Math.min(max - extent, max - extent - viewPosition.x));
			}
		    }
		}
		hsb.setValues(value, extent, 0, max);
	    }

	    if (rowHead != null) {
		Point p = rowHead.getViewPosition();
		p.y = viewport.getViewPosition().y;
                p.x = 0;
		rowHead.setViewPosition(p);
	    }

	    if (colHead != null) {
		Point p = colHead.getViewPosition();
		if (ltr) {
		    p.x = viewport.getViewPosition().x;
		} else {
		    p.x = Math.max(0, viewport.getViewPosition().x);
		}
                p.y = 0;
		colHead.setViewPosition(p);
	    }
	}
    }

    /**
     * Returns the baseline.
     *
     * @throws NullPointerException {@inheritDoc}
     * @throws IllegalArgumentException {@inheritDoc}
     * @see javax.swing.JComponent#getBaseline(int, int)
     * @since 1.6
     */
    public int getBaseline(JComponent c, int width, int height) {
        JViewport viewport = scrollpane.getViewport();
        Insets spInsets = scrollpane.getInsets();
        int y = spInsets.top;
        height = height - spInsets.top - spInsets.bottom;
        width = width - spInsets.left - spInsets.right;
        JViewport columnHeader = scrollpane.getColumnHeader();
        if (columnHeader != null && columnHeader.isVisible()) {
            Component header = columnHeader.getView();
            if (header != null && header.isVisible()) {
                // Header is always given it's preferred size.
                Dimension headerPref = header.getPreferredSize();
                int baseline = header.getBaseline(headerPref.width,
                                                  headerPref.height);
                if (baseline >= 0) {
                    return y + baseline;
                }
            }
            Dimension columnPref = columnHeader.getPreferredSize();
            height -= columnPref.height;
            y += columnPref.height;
        }
        Component view = (viewport == null) ? null : viewport.getView();
        if (view != null && view.isVisible() &&
                view.getBaselineResizeBehavior() ==
                Component.BaselineResizeBehavior.CONSTANT_ASCENT) {
            Border viewportBorder = scrollpane.getViewportBorder();
            if (viewportBorder != null) {
                Insets vpbInsets = viewportBorder.getBorderInsets(scrollpane);
                y += vpbInsets.top;
                height = height - vpbInsets.top - vpbInsets.bottom;
                width = width - vpbInsets.left - vpbInsets.right;
            }
            if (view.getWidth() > 0 && view.getHeight() > 0) {
                Dimension min = view.getMinimumSize();
                width = Math.max(min.width, view.getWidth());
                height = Math.max(min.height, view.getHeight());
            }
            if (width > 0 && height > 0) {
                int baseline = view.getBaseline(width, height);
                if (baseline > 0) {
                    return y + baseline;
                }
            }
        }
        return -1;
    }

    /**
     * Returns an enum indicating how the baseline of the component
     * changes as the size changes.
     *
     * @throws NullPointerException {@inheritDoc}
     * @see javax.swing.JComponent#getBaseline(int, int)
     * @since 1.6
     */
    public Component.BaselineResizeBehavior getBaselineResizeBehavior(
            JComponent c) {
        super.getBaselineResizeBehavior(c);
        // Baseline is either from the header, in which case it's always
        // the same size and therefor can be created as CONSTANT_ASCENT.
        // If the header doesn't have a baseline than the baseline will only
        // be valid if it's BaselineResizeBehavior is
        // CONSTANT_ASCENT, so, return CONSTANT_ASCENT.
        return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
    }


    /**
     * Listener for viewport events.
     */
    public class ViewportChangeHandler implements ChangeListener
    {

        // NOTE: This class exists only for backward compatability. All
        // its functionality has been moved into Handler. If you need to add
        // new functionality add it to the Handler, but make sure this      
        // class calls into the Handler.

	public void stateChanged(ChangeEvent e) {
            getHandler().stateChanged(e);
	}
    }

    protected ChangeListener createViewportChangeListener() {
	return getHandler();
    }


    /**
     * Horizontal scrollbar listener.
     */
    public class HSBChangeListener implements ChangeListener
    {

        // NOTE: This class exists only for backward compatability. All
        // its functionality has been moved into Handler. If you need to add
        // new functionality add it to the Handler, but make sure this      
        // class calls into the Handler.

	public void stateChanged(ChangeEvent e)
	{
            getHandler().stateChanged(e);
	}
    }

    /**
     * Returns a <code>PropertyChangeListener</code> that will be installed
     * on the horizontal <code>JScrollBar</code>.
     */
    private PropertyChangeListener createHSBPropertyChangeListener() {
        return getHandler();
    }

    protected ChangeListener createHSBChangeListener() {
	return getHandler();
    }


    /**
     * Vertical scrollbar listener.
     */
    public class VSBChangeListener implements ChangeListener
    {

        // NOTE: This class exists only for backward compatability. All
        // its functionality has been moved into Handler. If you need to add
        // new functionality add it to the Handler, but make sure this      
        // class calls into the Handler.

	public void stateChanged(ChangeEvent e)
	{
            getHandler().stateChanged(e);
	}
    }


    /**
     * Returns a <code>PropertyChangeListener</code> that will be installed
     * on the vertical <code>JScrollBar</code>.
     */
    private PropertyChangeListener createVSBPropertyChangeListener() {
        return getHandler();
    }

    protected ChangeListener createVSBChangeListener() {
	return getHandler();
    }

    /**
     * MouseWheelHandler is an inner class which implements the 
     * MouseWheelListener interface.  MouseWheelHandler responds to 
     * MouseWheelEvents by scrolling the JScrollPane appropriately.
     * If the scroll pane's
     * <code>isWheelScrollingEnabled</code>
     * method returns false, no scrolling occurs.
     * 
     * @see javax.swing.JScrollPane#isWheelScrollingEnabled
     * @see #createMouseWheelListener
     * @see java.awt.event.MouseWheelListener
     * @see java.awt.event.MouseWheelEvent
     * @since 1.4
     */
    protected class MouseWheelHandler implements MouseWheelListener {

        // NOTE: This class exists only for backward compatability. All
        // its functionality has been moved into Handler. If you need to add
        // new functionality add it to the Handler, but make sure this      
        // class calls into the Handler.

        /**
         * Called when the mouse wheel is rotated while over a
         * JScrollPane.
         *
         * @param e     MouseWheelEvent to be handled
         * @since 1.4
         */
        public void mouseWheelMoved(MouseWheelEvent e) {
            getHandler().mouseWheelMoved(e);
        }
    }

    /**
     * Creates an instance of MouseWheelListener, which is added to the
     * JScrollPane by installUI().  The returned MouseWheelListener is used
     * to handle mouse wheel-driven scrolling.
     *
     * @return      MouseWheelListener which implements wheel-driven scrolling
     * @see #installUI
     * @see MouseWheelHandler
     * @since 1.4
     */
    protected MouseWheelListener createMouseWheelListener() {
        return getHandler();
    }

    protected void updateScrollBarDisplayPolicy(PropertyChangeEvent e) {
	scrollpane.revalidate();
	scrollpane.repaint();
    }


    protected void updateViewport(PropertyChangeEvent e) 
    {
	JViewport oldViewport = (JViewport)(e.getOldValue());
	JViewport newViewport = (JViewport)(e.getNewValue());

	if (oldViewport != null) {
	    oldViewport.removeChangeListener(viewportChangeListener);
	}
	
	if (newViewport != null) {
	    Point p = newViewport.getViewPosition();
	    if (scrollpane.getComponentOrientation().isLeftToRight()) {
		p.x = Math.max(p.x, 0);
	    } else {
		int max = newViewport.getViewSize().width;
		int extent = newViewport.getExtentSize().width;
		if (extent > max) {
		    p.x = max - extent;
		} else {
		    p.x = Math.max(0, Math.min(max - extent, p.x));
		}
	    }
	    p.y = Math.max(p.y, 0);
	    newViewport.setViewPosition(p);
	    newViewport.addChangeListener(viewportChangeListener);
	}
    }


    protected void updateRowHeader(PropertyChangeEvent e) 
    {
	JViewport newRowHead = (JViewport)(e.getNewValue());
	if (newRowHead != null) {
	    JViewport viewport = scrollpane.getViewport();
	    Point p = newRowHead.getViewPosition();
	    p.y = (viewport != null) ? viewport.getViewPosition().y : 0;
	    newRowHead.setViewPosition(p);
	}
    }


    protected void updateColumnHeader(PropertyChangeEvent e) 
    {
	JViewport newColHead = (JViewport)(e.getNewValue());
	if (newColHead != null) {
	    JViewport viewport = scrollpane.getViewport();
	    Point p = newColHead.getViewPosition();
	    if (viewport == null) {
		p.x = 0;
	    } else {
		if (scrollpane.getComponentOrientation().isLeftToRight()) {
		    p.x = viewport.getViewPosition().x;
		} else {
		    p.x = Math.max(0, viewport.getViewPosition().x);
		}
	    }
	    newColHead.setViewPosition(p);
	    scrollpane.add(newColHead, COLUMN_HEADER);
	}
    }

    private void updateHorizontalScrollBar(PropertyChangeEvent pce) {
        updateScrollBar(pce, hsbChangeListener, hsbPropertyChangeListener);
    }

    private void updateVerticalScrollBar(PropertyChangeEvent pce) {
        updateScrollBar(pce, vsbChangeListener, vsbPropertyChangeListener);
    }

    private void updateScrollBar(PropertyChangeEvent pce, ChangeListener cl,
                                 PropertyChangeListener pcl) {
        JScrollBar sb = (JScrollBar)pce.getOldValue();
        if (sb != null) {
            if (cl != null) {
                sb.getModel().removeChangeListener(cl);
            }
            if (pcl != null) {
                sb.removePropertyChangeListener(pcl);
            }
        }
        sb = (JScrollBar)pce.getNewValue();
        if (sb != null) {
            if (cl != null) {
                sb.getModel().addChangeListener(cl);
            }
            if (pcl != null) {
                sb.addPropertyChangeListener(pcl);
            }
        }
    }

    public class PropertyChangeHandler implements PropertyChangeListener
    {

        // NOTE: This class exists only for backward compatability. All
        // its functionality has been moved into Handler. If you need to add
        // new functionality add it to the Handler, but make sure this      
        // class calls into the Handler.

        public void propertyChange(PropertyChangeEvent e)
        {
            getHandler().propertyChange(e);
	}
    }



    /**
     * Creates an instance of PropertyChangeListener that's added to 
     * the JScrollPane by installUI().  Subclasses can override this method
     * to return a custom PropertyChangeListener, e.g.
     * <pre>
     * class MyScrollPaneUI extends BasicScrollPaneUI {
     *    protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
     *        return new MyPropertyChangeListener();
     *    }
     *    public class MyPropertyChangeListener extends PropertyChangeListener {
     *        public void propertyChange(PropertyChangeEvent e) {
     *            if (e.getPropertyName().equals("viewport")) {
     *                // do some extra work when the viewport changes
     *            }
     *            super.propertyChange(e);
     *        }
     *    }
     * }
     * </pre>
     * 
     * @see java.beans.PropertyChangeListener
     * @see #installUI
     */
    protected PropertyChangeListener createPropertyChangeListener() {
        return getHandler();
    }


    private static class Actions extends UIAction {
        private static final String SCROLL_UP = "scrollUp";
        private static final String SCROLL_DOWN = "scrollDown";
        private static final String SCROLL_HOME = "scrollHome";
        private static final String SCROLL_END = "scrollEnd";
        private static final String UNIT_SCROLL_UP = "unitScrollUp";
        private static final String UNIT_SCROLL_DOWN = "unitScrollDown";
        private static final String SCROLL_LEFT = "scrollLeft";
        private static final String SCROLL_RIGHT = "scrollRight";
        private static final String UNIT_SCROLL_LEFT = "unitScrollLeft";
        private static final String UNIT_SCROLL_RIGHT = "unitScrollRight";


        Actions(String key) {
            super(key);
        }

        public void actionPerformed(ActionEvent e) {
            JScrollPane scrollPane = (JScrollPane)e.getSource();
            boolean ltr = scrollPane.getComponentOrientation().isLeftToRight();
            String key = getName();

            if (key == SCROLL_UP) {
                scroll(scrollPane, SwingConstants.VERTICAL, -1, true);
            }
            else if (key == SCROLL_DOWN) {
                scroll(scrollPane, SwingConstants.VERTICAL, 1, true);
            }
            else if (key == SCROLL_HOME) {
                scrollHome(scrollPane);
            }
            else if (key == SCROLL_END) {
                scrollEnd(scrollPane);
            }
            else if (key == UNIT_SCROLL_UP) {
                scroll(scrollPane, SwingConstants.VERTICAL, -1, false);
            }
            else if (key == UNIT_SCROLL_DOWN) {
                scroll(scrollPane, SwingConstants.VERTICAL, 1, false);
            }
            else if (key == SCROLL_LEFT) {
                scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1,
                       true);
            }
            else if (key == SCROLL_RIGHT) {
                scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1,
                       true);
            }
            else if (key == UNIT_SCROLL_LEFT) {
                scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? -1 : 1,
                       false);
            }
            else if (key == UNIT_SCROLL_RIGHT) {
                scroll(scrollPane, SwingConstants.HORIZONTAL, ltr ? 1 : -1,
                       false);
            }
        }

        private void scrollEnd(JScrollPane scrollpane) {
	    JViewport vp = scrollpane.getViewport();
	    Component view;
	    if (vp != null && (view = vp.getView()) != null) {
		Rectangle visRect = vp.getViewRect();
		Rectangle bounds = view.getBounds();
		if (scrollpane.getComponentOrientation().isLeftToRight()) {
		    vp.setViewPosition(new Point(bounds.width - visRect.width,
					     bounds.height - visRect.height));
		} else {
		    vp.setViewPosition(new Point(0, 
					     bounds.height - visRect.height));
		}
	    }
        }

        private void scrollHome(JScrollPane scrollpane) {
	    JViewport vp = scrollpane.getViewport();
	    Component view;
	    if (vp != null && (view = vp.getView()) != null) {
		if (scrollpane.getComponentOrientation().isLeftToRight()) {
		    vp.setViewPosition(new Point(0, 0));
		} else {
		    Rectangle visRect = vp.getViewRect();
		    Rectangle bounds = view.getBounds();
		    vp.setViewPosition(new Point(bounds.width - visRect.width, 0));
		}
            }
        }

	private void scroll(JScrollPane scrollpane, int orientation,
                            int direction, boolean block) {
	    JViewport vp = scrollpane.getViewport();
	    Component view;
	    if (vp != null && (view = vp.getView()) != null) {
		Rectangle visRect = vp.getViewRect();
		Dimension vSize = view.getSize();
		int amount;

		if (view instanceof Scrollable) {
		    if (block) {
			amount = ((Scrollable)view).getScrollableBlockIncrement
			         (visRect, orientation, direction);
		    }
		    else {
			amount = ((Scrollable)view).getScrollableUnitIncrement
			         (visRect, orientation, direction);
		    }
		}
		else {
		    if (block) {
			if (orientation == SwingConstants.VERTICAL) {
			    amount = visRect.height;
			}
			else {
			    amount = visRect.width;
			}
		    }
		    else {
			amount = 10;
		    }
		}
		if (orientation == SwingConstants.VERTICAL) {
		    visRect.y += (amount * direction);
		    if ((visRect.y + visRect.height) > vSize.height) {
			visRect.y = Math.max(0, vSize.height - visRect.height);
		    }
		    else if (visRect.y < 0) {
			visRect.y = 0;
		    }
		}
		else {
		    if (scrollpane.getComponentOrientation().isLeftToRight()) {
			visRect.x += (amount * direction);
			if ((visRect.x + visRect.width) > vSize.width) {
			    visRect.x = Math.max(0, vSize.width - visRect.width);
			} else if (visRect.x < 0) {
			    visRect.x = 0;
			}
		    } else {
			visRect.x -= (amount * direction);
                        if (visRect.width > vSize.width) {
                            visRect.x = vSize.width - visRect.width;
                        } else {
                            visRect.x = Math.max(0, Math.min(vSize.width - visRect.width, visRect.x));
			}
		    }
		}
		vp.setViewPosition(visRect.getLocation());
	    }
	}
    }


    class Handler implements ChangeListener, PropertyChangeListener, MouseWheelListener {
        //
        // MouseWheelListener
        //
        public void mouseWheelMoved(MouseWheelEvent e) {
            if (scrollpane.isWheelScrollingEnabled() &&
                e.getWheelRotation() != 0) {
                JScrollBar toScroll = scrollpane.getVerticalScrollBar();
                int direction = e.getWheelRotation() < 0 ? -1 : 1;
                int orientation = SwingConstants.VERTICAL;

                // find which scrollbar to scroll, or return if none
                if (toScroll == null || !toScroll.isVisible()) { 
                    toScroll = scrollpane.getHorizontalScrollBar();
                    if (toScroll == null || !toScroll.isVisible()) { 
                        return;
                    }
                    orientation = SwingConstants.HORIZONTAL;
                }

                e.consume();

                if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {
                    JViewport vp = scrollpane.getViewport();
                    if (vp == null) { return; }
                    Component comp = vp.getView();
                    int units = Math.abs(e.getUnitsToScroll());

                    // When the scrolling speed is set to maximum, it's possible
                    // for a single wheel click to scroll by more units than
                    // will fit in the visible area.  This makes it
                    // hard/impossible to get to certain parts of the scrolling
                    // Component with the wheel.  To make for more accurate
                    // low-speed scrolling, we limit scrolling to the block
                    // increment if the wheel was only rotated one click.
                    boolean limitScroll = Math.abs(e.getWheelRotation()) == 1;

                    // Check if we should use the visibleRect trick
                    Object fastWheelScroll = toScroll.getClientProperty(
                                               "JScrollBar.fastWheelScrolling");
                    if (Boolean.TRUE == fastWheelScroll &&
                        comp instanceof Scrollable) {
                        // 5078454: Under maximum acceleration, we may scroll
                        // by many 100s of units in ~1 second.
                        //
                        // BasicScrollBarUI.scrollByUnits() can bog down the EDT
                        // with repaints in this situation.  However, the
                        // Scrollable interface allows us to pass in an
                        // arbitrary visibleRect.  This allows us to accurately
                        // calculate the total scroll amount, and then update
                        // the GUI once.  This technique provides much faster
                        // accelerated wheel scrolling.
                        Scrollable scrollComp = (Scrollable) comp;
                        Rectangle viewRect = vp.getViewRect();
                        int startingX = viewRect.x;
                        boolean leftToRight =
                                 comp.getComponentOrientation().isLeftToRight();
                        int scrollMin = toScroll.getMinimum();
                        int scrollMax = toScroll.getMaximum() -
                                        toScroll.getModel().getExtent();

                        if (limitScroll) {
                            int blockIncr =
                                scrollComp.getScrollableBlockIncrement(viewRect,
                                                                    orientation,
                                                                    direction);
                            if (direction < 0) {
                                scrollMin = Math.max(scrollMin,
                                               toScroll.getValue() - blockIncr);
                            }
                            else {
                                scrollMax = Math.min(scrollMax,
                                               toScroll.getValue() + blockIncr);
                            }
                        }

                        for (int i = 0; i < units; i++) {
                            int unitIncr =
                                scrollComp.getScrollableUnitIncrement(viewRect,
                                                        orientation, direction);
                            // Modify the visible rect for the next unit, and
                            // check to see if we're at the end already.
                            if (orientation == SwingConstants.VERTICAL) {
                                if (direction < 0) {
                                    viewRect.y -= unitIncr;
                                    if (viewRect.y <= scrollMin) {
                                        viewRect.y = scrollMin;
                                        break;
                                    }
                                }
                                else { // (direction > 0
                                    viewRect.y += unitIncr;
                                    if (viewRect.y >= scrollMax) {
                                        viewRect.y = scrollMax;
                                        break;
                                    }
                                }
                            }
                            else {
                                // Scroll left
                                if ((leftToRight && direction < 0) ||
                                    (!leftToRight && direction > 0)) {
                                    viewRect.x -= unitIncr;
                                    if (leftToRight) {
                                        if (viewRect.x < scrollMin) {
                                            viewRect.x = scrollMin;
                                            break;
                                        }
                                    }
                                }
                                // Scroll right
                                else if ((leftToRight && direction > 0) ||
                                    (!leftToRight && direction < 0)) {
                                    viewRect.x += unitIncr;
                                    if (leftToRight) {
                                        if (viewRect.x > scrollMax) {
                                            viewRect.x = scrollMax;
                                            break;
                                        }
                                    }
                                }
                                else {
                                    assert false : "Non-sensical ComponentOrientation / scroll direction";
                                }
                            }
                        }
                        // Set the final view position on the ScrollBar
                        if (orientation == SwingConstants.VERTICAL) {
                            toScroll.setValue(viewRect.y);
                        }
                        else {
                            if (leftToRight) {
                                toScroll.setValue(viewRect.x);
                            }
                            else {
                                // rightToLeft scrollbars are oriented with 
                                // minValue on the right and maxValue on the
                                // left.
                                int newPos = toScroll.getValue() -
                                                       (viewRect.x - startingX);
                                if (newPos < scrollMin) {
                                    newPos = scrollMin;
                                }
                                else if (newPos > scrollMax) {
                                    newPos = scrollMax;
                                }
                                toScroll.setValue(newPos);
                            }
                        }
                    }
                    else {
                        // Viewport's view is not a Scrollable, or fast wheel
                        // scrolling is not enabled.
                        BasicScrollBarUI.scrollByUnits(toScroll, direction,
                                                       units, limitScroll);
                    }
                }
                else if (e.getScrollType() ==
                         MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
                    BasicScrollBarUI.scrollByBlock(toScroll, direction);
                }
            }
        }

        //
        // ChangeListener: This is added to the vieport, and hsb/vsb models.
        //
	public void stateChanged(ChangeEvent e) {
            JViewport viewport = scrollpane.getViewport();

            if (viewport != null) {
                if (e.getSource() == viewport) {
                    viewportStateChanged(e);
                }
                else {
                    JScrollBar hsb = scrollpane.getHorizontalScrollBar();
                    if (hsb != null && e.getSource() == hsb.getModel()) {
                        hsbStateChanged(viewport, e);
                    }
                    else {
                        JScrollBar vsb = scrollpane.getVerticalScrollBar();
                        if (vsb != null && e.getSource() == vsb.getModel()) {
                            vsbStateChanged(viewport, e);
                        }
                    }
                }
            }
        }

        private void vsbStateChanged(JViewport viewport, ChangeEvent e) {
            BoundedRangeModel model = (BoundedRangeModel)(e.getSource());
            Point p = viewport.getViewPosition();
            p.y = model.getValue();
            viewport.setViewPosition(p);
        }

        private void hsbStateChanged(JViewport viewport, ChangeEvent e) {
            BoundedRangeModel model = (BoundedRangeModel)(e.getSource());
            Point p = viewport.getViewPosition();
            int value = model.getValue();
            if (scrollpane.getComponentOrientation().isLeftToRight()) {
                p.x = value;
            } else {
                int max = viewport.getViewSize().width;
                int extent = viewport.getExtentSize().width;
                int oldX = p.x;

                /* Set new X coordinate based on "value".
                 */
                p.x = max - extent - value;

                /* If setValue() was called before "extent" was fixed,
                 * turn setValueCalled flag on.
                 */
                if ((extent == 0) && (value != 0) && (oldX == max)) {
                    setValueCalled = true;
                } else {
                    /* When a pane without a horizontal scroll bar was
                     * reduced and the bar appeared, the viewport should
                     * show the right side of the view.
                     */
                    if ((extent != 0) && (oldX < 0) && (p.x == 0)) {
                        p.x += value;
                    }
                }
            }
            viewport.setViewPosition(p);
        }

        private void viewportStateChanged(ChangeEvent e) {
            syncScrollPaneWithViewport();
	}


        //
        // PropertyChangeListener: This is installed on both the JScrollPane
        // and the horizontal/vertical scrollbars.
        //

        // Listens for changes in the model property and reinstalls the
        // horizontal/vertical PropertyChangeListeners.
        public void propertyChange(PropertyChangeEvent e) {
            if (e.getSource() == scrollpane) {
                scrollPanePropertyChange(e);
            }
            else {
                sbPropertyChange(e);
            }
        }

        private void scrollPanePropertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();

	    if (propertyName == "verticalScrollBarDisplayPolicy") {
		updateScrollBarDisplayPolicy(e);
	    }
	    else if (propertyName == "horizontalScrollBarDisplayPolicy") {
		updateScrollBarDisplayPolicy(e);
	    }
	    else if (propertyName == "viewport") {
		updateViewport(e);
	    }
	    else if (propertyName == "rowHeader") {
		updateRowHeader(e);
	    }
	    else if (propertyName == "columnHeader") {
		updateColumnHeader(e);
	    }
	    else if (propertyName == "verticalScrollBar") {
		updateVerticalScrollBar(e);
	    }
	    else if (propertyName == "horizontalScrollBar") {
		updateHorizontalScrollBar(e);
	    }
	    else if (propertyName == "componentOrientation") {
		scrollpane.revalidate();
		scrollpane.repaint();
	    }
        }

        // PropertyChangeListener for the horizontal and vertical scrollbars.
        private void sbPropertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            Object source = e.getSource();

            if ("model" == propertyName) {
                JScrollBar sb = scrollpane.getVerticalScrollBar();
                BoundedRangeModel oldModel = (BoundedRangeModel)e.
                                     getOldValue();
                ChangeListener cl = null;

                if (source == sb) {
                    cl = vsbChangeListener;
                }
                else if (source == scrollpane.getHorizontalScrollBar()) {
                    sb = scrollpane.getHorizontalScrollBar();
                    cl = hsbChangeListener;
                }
                if (cl != null) {
                    if (oldModel != null) {
                        oldModel.removeChangeListener(cl);
                    }
                    if (sb.getModel() != null) {
                        sb.getModel().addChangeListener(cl);
                    }
                }
            }
            else if ("componentOrientation" == propertyName) {
                if (source == scrollpane.getHorizontalScrollBar()) {
		    JScrollBar hsb = scrollpane.getHorizontalScrollBar();
 		    JViewport viewport = scrollpane.getViewport();
                    Point p = viewport.getViewPosition();
                    if (scrollpane.getComponentOrientation().isLeftToRight()) {
                        p.x = hsb.getValue();
                    } else {
                        p.x = viewport.getViewSize().width - viewport.getExtentSize().width - hsb.getValue();
                    }
                    viewport.setViewPosition(p);
                }
            }
        }
    }
}
